﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using DeSleeper.Library.Properties;

namespace DeSleeper.Library
{
    public class SelfFlatteningException : Exception
    {
        private readonly string _message;
        public SelfFlatteningException(string message, Exception innerException) : base(string.Empty, innerException)
        {
            Debug.Assert(innerException != null);

            _message = message;
        }

        public Type ExceptionType
        {
            get
            {
                var innerException = InnerException;
                while (innerException is SelfFlatteningException)
                {
                    innerException = innerException.InnerException;
                }
                // Disallowed by constructor assertions, but I avoid having code which may throw exceptions in exception handling code.
                if (innerException == null) return typeof (SelfFlatteningException);
                return innerException.GetType();
            }
        }

        public override string Message
        {
            get
            {
                var messageBuilder = new StringBuilder();
                Exception nextException = this;
                var selfException = this;
                while (selfException != null)
                {
                    if (!string.IsNullOrEmpty(selfException._message))
                    {
                        messageBuilder.Append(selfException._message);
                        messageBuilder.Append(" ");
                    }
                    nextException = nextException.InnerException;
                    selfException = nextException as SelfFlatteningException;
                    
                }
                
                // nextException should always be non-null, but I avoid having code which may throw exceptions in exception handling code.
                if (nextException != null)
                {
                    var aggregateException = nextException as AggregateException;
                    if (aggregateException == null || (aggregateException is SelfFlatteningAggregateException))
                    {
                        messageBuilder.Append(nextException.Message);
                    }
                    //else if (aggregateException.InnerExceptions.Count == 1)
                    //{
                    //    messageBuilder.Append(aggregateException.InnerExceptions[0].Message);
                    //}
                    else
                    {
                        messageBuilder.Append(aggregateException.Message);
                        for (int i = 0; i < aggregateException.InnerExceptions.Count; i++)
                        {
                            messageBuilder.AppendFormat(CultureInfo.InvariantCulture,
                                                        Resources.AggregateExceptionMessageFormat,
                                                        Environment.NewLine,
                                                        aggregateException.InnerExceptions[i].Message);
                        }
                    }
                }
                return messageBuilder.ToString();
            }
        }

        public override string ToString()
        {
            var stringBuilder = new StringBuilder();
            var flattenableExceptions = new Stack<Exception>();

            Exception nextException = this;
            var selfException = this;
            while (selfException != null)
            {
                flattenableExceptions.Push(nextException);
                if (!string.IsNullOrEmpty(selfException._message))
                {
                    stringBuilder.Append(selfException._message);
                    stringBuilder.Append(" ");
                }
                nextException = nextException.InnerException;
                selfException = nextException as SelfFlatteningException;
            }

            // nextException should always be non-null, but I avoid having code which may throw exceptions in exception handling code.
            if (nextException != null)
            {
                var baseExceptionString = nextException.ToString();
                var messageIndex = baseExceptionString.IndexOf(nextException.Message);
                if (messageIndex != -1)
                {
                    stringBuilder.Insert(0, baseExceptionString.Substring(0, messageIndex));
                    stringBuilder.Append(baseExceptionString.Substring(messageIndex));
                }
                else
                {
                    stringBuilder.Append(baseExceptionString);
                }
            }

            foreach (var exception in flattenableExceptions)
            {
                var stackTrace = exception.StackTrace;
                if (!string.IsNullOrEmpty(stackTrace))
                {
                    var line1EndIndex = stackTrace.IndexOf(Environment.NewLine);
                    if (line1EndIndex != -1)
                    {
                        stringBuilder.AppendLine();
                        stringBuilder.Append(stackTrace.Substring(line1EndIndex + Environment.NewLine.Length));
                    }
                }
            }
            return stringBuilder.ToString();
        }
    }
}
